package getfrom

import (
	"errors"
	"fmt"
	"gitee.com/ymofen/gobase"
	"io"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
)

type FuncGetFrom func(args ...interface{}) (data []byte, err error)

type factoryFunc struct {
	lk  sync.RWMutex
	lst map[string]FuncGetFrom
}

var (
	defFactory  *factoryFunc = &factoryFunc{lst: make(map[string]FuncGetFrom)}
	ErrNotFound              = errors.New("Func Not Found!!!!")
)

func GetFunc(id string) FuncGetFrom {
	defFactory.lk.RLock()
	defer defFactory.lk.RUnlock()
	return defFactory.lst[id]
}

func Register(id string, fn FuncGetFrom) {
	defFactory.lk.Lock()
	defFactory.lst[id] = fn
	defFactory.lk.Unlock()
}

func UnRegister(id string) {
	defFactory.lk.Lock()
	delete(defFactory.lst, id)
	defFactory.lk.Unlock()
}

func TryRegister(id string, fn FuncGetFrom) bool {
	defFactory.lk.Lock()
	defer defFactory.lk.Unlock()
	if _, ok := defFactory.lst[id]; !ok {
		defFactory.lst[id] = fn
		return true
	}
	return false
}

func TryUnregister(id string) bool {
	defFactory.lk.Lock()
	defer defFactory.lk.Unlock()
	if _, ok := defFactory.lst[id]; !ok {
		delete(defFactory.lst, id)
		return true
	}
	return false
}

func GetFrom(id string, args ...interface{}) (data []byte, err error) {
	fn := GetFunc(id)
	if fn == nil {
		return nil, ErrNotFound
	}
	return fn(args...)
}

func GetFromS(s string) (data []byte, err error) {
	idx := strings.Index(s, "://")
	if idx == -1 {
		return nil, ErrNotFound
	}

	return GetFrom(s[:idx], s[idx+3:])
}

type GetFromInstance struct {
	lk  sync.RWMutex
	obj interface{}
	id  string
	arg string
	s   string
	fn  FuncGetFrom
}

var (
	instanceAliveN int32 = 0
)

func GetFromInstanceAliveN() int32 {
	return atomic.LoadInt32(&instanceAliveN)
}

func NewGetFromInstance() *GetFromInstance {
	rval := &GetFromInstance{}
	atomic.AddInt32(&instanceAliveN, 1)
	runtime.SetFinalizer(rval, func(obj interface{}) {
		atomic.AddInt32(&instanceAliveN, -1)
	})
	return rval
}

func (this *GetFromInstance) closeInner() {
	if this.obj != nil {
		if ref, ok := this.obj.(*gobase.ObjectInnerRef); ok {
			ref.ReleaseRef()
		} else if c, ok := this.obj.(io.Closer); ok {
			c.Close()
		}
		this.obj = nil
	}
}

func (this *GetFromInstance) Close() {
	this.lk.Lock()
	defer this.lk.Unlock()
	this.closeInner()
	this.id = ""
	this.arg = ""
	this.fn = nil
}

func (this *GetFromInstance) Valid() bool {
	this.lk.RLock()
	defer this.lk.RUnlock()
	return this.fn != nil
}

func (this *GetFromInstance) GetS() string {
	this.lk.RLock()
	defer this.lk.RUnlock()
	return this.s
}

func (this *GetFromInstance) GetConf() (id string, arg string) {
	this.lk.RLock()
	defer this.lk.RUnlock()
	return this.id, this.arg
}

func (this *GetFromInstance) Update(s string) (id string, arg string, changed bool) {
	this.lk.Lock()
	defer this.lk.Unlock()
	if s != this.s || this.fn == nil {
		this.closeInner()
		this.s = s
		this.id = ""
		this.fn = nil
		this.arg = ""
		changed = true
		if len(s) > 0 {
			id, arg = gobase.Split2Str(s, "://")
			this.id, this.arg = id, arg
			this.fn = GetFunc(this.id)
			// 直接获取内置的操作对象
			cid := fmt.Sprintf("getfrom.instance.%s", this.id)
			if rval, err := gobase.CreateFactoryInstance(cid, arg); err == nil {
				this.obj = rval
			}
		}
		return
	} else {
		id, arg = this.id, this.arg
		changed = false
		return
	}
}

// 使用传入的参数进行调用
func (this *GetFromInstance) GetFrom() ([]byte, error) {
	return this.GetFromArg(this.arg)
}

// s 为完整字符串, type 必须等于this.id
func (this *GetFromInstance) GetFromS(s string) ([]byte, error) {
	id, arg := gobase.Split2Str(s, "://")
	if id != this.id {
		return nil, fmt.Errorf("invalid %s!=%s", id, this.id)
	}
	return this.GetFromArg(arg)
}

// 使用arg作为第一个参数
func (this *GetFromInstance) GetFromArg(arg interface{}) ([]byte, error) {
	this.lk.RLock()
	defer this.lk.RUnlock()
	if this.fn != nil {
		if this.obj == nil {
			return this.fn(arg)
		} else {
			return this.fn(this.obj, arg)
		}
	}
	if this.obj == nil {
		return GetFrom(this.id, arg)
	} else {
		return GetFrom(this.id, this.obj, arg)
	}
}

// 使用args...作为参数
func (this *GetFromInstance) GetFromArgs(args ...interface{}) ([]byte, error) {
	this.lk.RLock()
	defer this.lk.RUnlock()
	var args0 []interface{}
	if this.obj != nil {
		args0 = append(args0, this.obj)
		args0 = append(args0, args...)
	} else {
		args0 = args
	}
	if this.fn != nil {
		return this.fn(args0)
	}
	return GetFrom(this.id, args0)
}
